﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.IO;
using CodeIn.Core.Modules;
using System.Diagnostics;
using AurelienRibon.Core.Options;
using CodeIn.Core;

namespace CodeIn.Modules.Interop.GraphLabInterop {
	public class GraphLabInterop : BaseModule {
		private enum Modes { UNDEFINED, START, STOP, EXEC }
		private TcpListener server;
		private TcpClient client;

		protected override void InitializeOverride() {
			server = new TcpListener(IPAddress.Loopback, 0);

			Option opt = new Option(
				"External.Paths.GraphLabJarFilePath", 
				@"C:\Users\Aurelien\Home\Dev\Sources\Java\GraphLab\dist\GraphLab.jar", 
				false, null, null);

			OptionManager.Instance.RegisterOption(opt);
		}

		protected override bool CanExecuteOverride(string[] args) {
			string command_start = Infos.Commands[0];
			string command_stop = Infos.Commands[1];
			string command_exec = Infos.Commands[2];
			string help = "Valid execution schemes are as follows:\n"
				+ "> " + command_start + "\n"
				+ "> " + command_stop + "\n"
				+ "> " + command_exec + " {command} [arguments]*";

			Modes mode = GetCurrentMode(args);
			if (mode == Modes.UNDEFINED
				|| (mode == Modes.EXEC && args.Length == 1))
				return False(help);

			return true;
		}

		protected override void ExecuteOverride(string[] args) {
			Modes mode = GetCurrentMode(args);
			switch (mode) {
				case Modes.START:
					StartProcess();
					StartServer();
					break;

				case Modes.STOP:
					StopServer();
					break;

				case Modes.EXEC:
					string msg = args[1];
					if (args.Length == 3) {
						msg += "(" + args[2] + ")";
					} else if (args.Length > 3) {
						msg += "(" + args[2];
						for (int i = 3; i < args.Length; i++)
							msg += ", " + args[i];
						msg += ")";
					}
					ExecuteCommand(msg);
					break;
			}
		}

		private Modes GetCurrentMode(string[] args) {
			if (args[0].EndsWith("-start", StringComparison.InvariantCultureIgnoreCase))
				return Modes.START;
			else if (args[0].EndsWith("-stop", StringComparison.InvariantCultureIgnoreCase))
				return Modes.STOP;
			else if (args[0].EndsWith("-exec", StringComparison.InvariantCultureIgnoreCase))
				return Modes.EXEC;
			return Modes.UNDEFINED;
		}

		// --------------------------------------------------------------------
		// START/STOP/EXEC methods
		// --------------------------------------------------------------------

		private void StartProcess() {
			var endPoint = server.LocalEndpoint as IPEndPoint;

			string glabPath = OptionManager.Instance.GetValueFromName("External.Paths.GraphLabJarFile");
			ProcessStartInfo infos = new ProcessStartInfo("java", "-jar " + glabPath + " -codein-interop " + endPoint.Port);
			infos.RedirectStandardOutput = true;
			infos.RedirectStandardError = true;
			infos.UseShellExecute = false;

			Process process = Process.Start(infos);
			process.OutputDataReceived += (s, e) => { LocalDebug.WriteLine(e.Data); };
			process.ErrorDataReceived += (s, e) => { LocalDebug.WriteLine(e.Data); };
			process.BeginOutputReadLine();
			process.BeginErrorReadLine();
		}

		private void StartServer() {
			server.Start();
			var endPoint = server.LocalEndpoint as IPEndPoint;
			LocalTrace.WriteLine("Server started on {0} : {1}.", endPoint.Address.ToString(), endPoint.Port);
			LocalTrace.WriteLine("Waiting for GraphLab client to connect...");
			client = server.AcceptTcpClient();
			LocalTrace.WriteLine("Client connected !");

			string welcomeMsg = ReadFromStream(client.GetStream());

			LocalTrace.WriteLine("Welcome message is: ");
			LocalTrace.Indent();
			LocalTrace.WriteLine("\n" + welcomeMsg + (welcomeMsg.EndsWith("\n") ? "" : "\n"));
			LocalTrace.Unindent();
		}

		private void StopServer() {
			if (client == null) {
				LocalTrace.WriteErrorLine("Client is null.");
				return;
			}
			client.Close();
			client = null;
			server.Stop();
			LocalTrace.WriteLine("Server stopped.");
		}

		private void ExecuteCommand(string command) {
			if (client == null) {
				LocalTrace.WriteErrorLine("Client is null.");
				return;
			}
			if (!client.Connected) {
				LocalTrace.WriteErrorLine("Client is disconnected.");
				return;
			}

			string commandSent = command + (command.EndsWith("\n") ? "" : "\n");

			LocalTrace.WriteLine("Sending command:");
			LocalTrace.Indent();
			LocalTrace.WriteLine(commandSent);
			LocalTrace.Unindent();

			WriteToStream(client.GetStream(), commandSent);

			LocalTrace.WriteLine("Response is:");
			LocalTrace.Indent();
			LocalTrace.WriteLine(ReadFromStream(client.GetStream()).TrimEnd());
			LocalTrace.Unindent();
		}

		// --------------------------------------------------------------------
		// Utilities
		// --------------------------------------------------------------------

		/// <summary>
		/// Returns the stream content as a string.
		/// </summary>
		private string ReadFromStream(NetworkStream stream) {
			string ret = "";
			byte[] buffer = new byte[1];

			do {
				stream.Read(buffer, 0, buffer.Length);
				ret += Encoding.Default.GetString(buffer);
			} while (stream.DataAvailable);

			return ret;
		}

		/// <summary>
		/// Writes in a stream and flushes it.
		/// </summary>
		private void WriteToStream(NetworkStream stream, string message) {
			StreamWriter writer = new StreamWriter(stream);
			writer.Write(message);
			writer.Flush();
			stream.Flush();
		}
	}
}
